{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Semantic Kernel \n",
    "\n",
    "In this code sample, you will use the [Semantic Kernel](https://aka.ms/ai-agents-beginners/semantic-kernel) AI Framework to create a basic agent. \n",
    "\n",
    "The goal of this sample is to show you the steps that we will later use in the addtional code samples when implementing the different agentic patterns. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Import the Needed Python Packages "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os \n",
    "from typing import Annotated\n",
    "from openai import AsyncOpenAI\n",
    "\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "\n",
    "\n",
    "from semantic_kernel.kernel import Kernel\n",
    "from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion\n",
    "from semantic_kernel.agents import ChatCompletionAgent\n",
    "from semantic_kernel.contents import ChatHistory\n",
    "\n",
    "\n",
    "from semantic_kernel.agents.open_ai import OpenAIAssistantAgent\n",
    "from semantic_kernel.contents import AuthorRole, ChatMessageContent\n",
    "from semantic_kernel.functions import kernel_function\n",
    "\n",
    "from semantic_kernel.connectors.ai import FunctionChoiceBehavior\n",
    "\n",
    "from semantic_kernel.contents.function_call_content import FunctionCallContent\n",
    "from semantic_kernel.contents.function_result_content import FunctionResultContent\n",
    "from semantic_kernel.functions import KernelArguments, kernel_function"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Creating the Client and Kernel \n",
    "\n",
    "In this sample, we will use [GitHub Models](https://aka.ms/ai-agents-beginners/github-models) for access to the LLM. \n",
    "\n",
    "The `ai_model_id` is defined as `gpt-4o-mini`. Try changing the model to another model available on the GitHub Models marketplace to see the different results. \n",
    "\n",
    "For us to us the `Azure Inference SDK` that is used for the `base_url` for GitHub Models, we will use the `AsyncOpenAI` connector within Semantic Kernel. There are also other [available connectors](https://learn.microsoft.com/semantic-kernel/concepts/ai-services/chat-completion) to use Semantic Kernel for other model providers.\n",
    "\n",
    "We will also create a `Kernel`. A `kernel` is a collection of the services and plugins that will be used by your Agents. In this snipppet, we are creating the kernel and adding the `chat_completion_service` to it.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random   \n",
    "\n",
    "# Define a sample plugin for the sample\n",
    "\n",
    "class DestinationsPlugin:\n",
    "    \"\"\"A List of Random Destinations for a vacation.\"\"\"\n",
    "\n",
    "    def __init__(self):\n",
    "        # List of vacation destinations\n",
    "        self.destinations = [\n",
    "            \"Barcelona, Spain\",\n",
    "            \"Paris, France\",\n",
    "            \"Berlin, Germany\",\n",
    "            \"Tokyo, Japan\",\n",
    "            \"Sydney, Australia\",\n",
    "            \"New York, USA\",\n",
    "            \"Cairo, Egypt\",\n",
    "            \"Cape Town, South Africa\",\n",
    "            \"Rio de Janeiro, Brazil\",\n",
    "            \"Bali, Indonesia\"\n",
    "        ]\n",
    "        # Track last destination to avoid repeats\n",
    "        self.last_destination = None\n",
    "\n",
    "    @kernel_function(description=\"Provides a random vacation destination.\")\n",
    "    def get_random_destination(self) -> Annotated[str, \"Returns a random vacation destination.\"]:\n",
    "        # Get available destinations (excluding last one if possible)\n",
    "        available_destinations = self.destinations.copy()\n",
    "        if self.last_destination and len(available_destinations) > 1:\n",
    "            available_destinations.remove(self.last_destination)\n",
    "\n",
    "        # Select a random destination\n",
    "        destination = random.choice(available_destinations)\n",
    "\n",
    "        # Update the last destination\n",
    "        self.last_destination = destination\n",
    "\n",
    "        return destination"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "load_dotenv()\n",
    "client = AsyncOpenAI(\n",
    "    api_key=os.environ.get(\"GITHUB_TOKEN\"), base_url=\"https://models.inference.ai.azure.com/\")\n",
    "\n",
    "kernel = Kernel()\n",
    "kernel.add_plugin(DestinationsPlugin(), plugin_name=\"destinations\")\n",
    "\n",
    "service_id = \"agent\"\n",
    "\n",
    "chat_completion_service = OpenAIChatCompletion(\n",
    "    ai_model_id=\"gpt-4o-mini\",\n",
    "    async_client=client,\n",
    "    service_id=service_id\n",
    ")\n",
    "kernel.add_service(chat_completion_service)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Creating the Agent \n",
    "\n",
    "Below we will are creating the Agent called `TravelAgent` and also creating a variable called `AGENT_INSTRUCTIONS`. We will later add this to our `system_message` that will give the agent instructions on the task, behavior and tone.\n",
    "\n",
    "For this example, we are using very simple instructions. You can change these instructions to see how the agent responds differently. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "settings = kernel.get_prompt_execution_settings_from_service_id(\n",
    "    service_id=service_id)\n",
    "settings.function_choice_behavior = FunctionChoiceBehavior.Auto()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "AGENT_NAME = \"TravelAgent\"\n",
    "AGENT_INSTRUCTIONS = \"You are a helpful AI Agent that can help plan vacations for customers at random destinations\"\n",
    "agent = ChatCompletionAgent(\n",
    "    service_id=service_id, \n",
    "    kernel=kernel, \n",
    "    name=AGENT_NAME,\n",
    "    instructions=AGENT_INSTRUCTIONS,\n",
    "    arguments=KernelArguments(settings=settings)\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Running the Agents \n",
    "\n",
    "Now we can run the Agent by defining the `ChatHistory` and adding the `system_message` to it. We will use the `AGENT_INSTRUCTIONS` that we defined earlier. \n",
    "\n",
    "After these are defined, we create a `user_inputs` that will be what the user is sending to the agent. In this case, we have set this message to `Plan me a sunny vacation`. \n",
    "\n",
    "Feel free to change this message to see how the agent responds differently. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div style='margin-bottom:10px'><div style='font-weight:bold'>User:</div><div style='margin-left:20px'>Plan me a day trip.</div></div><div style='margin-bottom:10px'><details><summary style='cursor:pointer; font-weight:bold; color:#0066cc;'>Function Calls (click to expand)</summary><div style='margin:10px; padding:10px; background-color:#f8f8f8; border:1px solid #ddd; border-radius:4px; white-space:pre-wrap;'>Calling: get_random_destination()<br>Calling: ({})<br>Result: Rio de Janeiro, Brazil</div></details></div><div style='margin-bottom:20px'><div style='font-weight:bold'>TravelAgent:</div><div style='margin-left:20px; white-space:pre-wrap'>I've planned a day trip to the beautiful city of Rio de Janeiro, Brazil! Here’s a suggested itinerary for your day:\n",
       "\n",
       "### Morning- **Breakfast**: Start your day with a traditional Brazilian breakfast of pão de queijo (cheese bread) and fresh tropical fruit at a local café.\n",
       "- **Visit Christ the Redeemer**: Head to the iconic Christ the Redeemer statue. You can take a train through the Tijuca Forest to reach the summit, and enjoy stunning views of the city along the way.\n",
       "\n",
       "### Midday- **Lunch**: Enjoy a leisurely lunch at a local restaurant in the Santa Teresa neighborhood. Try some feijoada, a hearty Brazilian stew.\n",
       "- **Explore Santa Teresa**: Stroll through the charming streets of Santa Teresa, filled with art galleries, colorful houses, and beautiful views.\n",
       "\n",
       "### Afternoon- **Sugarloaf Mountain**: Take the cable car to the top of Sugarloaf Mountain for panoramic views of Rio, including Copacabana and the sprawling coastline.\n",
       "- **Relax at Copacabana Beach**: Spend some time relaxing on the famous Copacabana Beach. If you're up for it, you can enjoy a refreshing swim or try out beach volleyball with locals.\n",
       "\n",
       "### Evening- **Dinner**: Dine at a seafood restaurant with a view of the beach, enjoying fresh catch of the day and a caipirinha cocktail, a Brazilian favorite.\n",
       "- **Nightlife**: If you’re up for it, explore the lively nightlife of Rio in Lapa, where you can find samba clubs and live music.\n",
       "\n",
       "Enjoy your day trip to Rio de Janeiro!</div></div><hr>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div style='margin-bottom:10px'><div style='font-weight:bold'>User:</div><div style='margin-left:20px'>I don't like that destination. Plan me another vacation.</div></div><div style='margin-bottom:10px'><details><summary style='cursor:pointer; font-weight:bold; color:#0066cc;'>Function Calls (click to expand)</summary><div style='margin:10px; padding:10px; background-color:#f8f8f8; border:1px solid #ddd; border-radius:4px; white-space:pre-wrap;'>Calling: get_random_destination()<br>Calling: ({})<br>Result: Cape Town, South Africa</div></details></div><div style='margin-bottom:20px'><div style='font-weight:bold'>TravelAgent:</div><div style='margin-left:20px; white-space:pre-wrap'>How about a day trip to stunning Cape Town, South Africa? Here's a suggested itinerary:\n",
       "\n",
       "### Morning- **Breakfast**: Start your day with a hearty breakfast at a café along the V&A Waterfront, enjoying views of the harbor and Table Mountain.\n",
       "- **Table Mountain**: Take the cable car up to Table Mountain for breathtaking panoramic views of the city and the ocean. Don’t forget your camera!\n",
       "\n",
       "### Midday- **Lunch**: Enjoy lunch at one of the waterfront restaurants, where you can savor fresh seafood or local South African dishes.\n",
       "- **Visit Kirstenbosch National Botanical Garden**: Explore these beautiful gardens nestled at the foot of Table Mountain. Walk along the treetop canopy walkway for a unique perspective of the flora.\n",
       "\n",
       "### Afternoon- **Explore the Cape Peninsula**: Take a scenic drive along Chapman's Peak Drive to Cape Point. Here, you can hike to the lighthouse and enjoy spectacular ocean views.\n",
       "- **Boulders Beach**: Afterward, head to Boulders Beach to see the famous African penguin colony.\n",
       "\n",
       "### Evening- **Dinner**: Return to Cape Town for dinner at a restaurant in the city center or along the waterfront, where you can enjoy a fine dining experience featuring local wines and cuisine.\n",
       "- **Sunset at Signal Hill**: End your day by watching the sunset from Signal Hill, offering a stunning view of the city and the ocean.\n",
       "\n",
       "Enjoy your day trip in Cape Town!</div></div><hr>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import display, HTML\n",
    "\n",
    "\n",
    "async def main():\n",
    "    # Define the chat history\n",
    "    chat_history = ChatHistory()\n",
    "\n",
    "    # Respond to user input\n",
    "    user_inputs = [\n",
    "        \"Plan me a day trip.\",\n",
    "        \"I don't like that destination. Plan me another vacation.\",\n",
    "    ]\n",
    "\n",
    "    for user_input in user_inputs:\n",
    "        # Add the user input to the chat history\n",
    "        chat_history.add_user_message(user_input)\n",
    "\n",
    "        # Start building HTML output\n",
    "        html_output = f\"<div style='margin-bottom:10px'>\"\n",
    "        html_output += f\"<div style='font-weight:bold'>User:</div>\"\n",
    "        html_output += f\"<div style='margin-left:20px'>{user_input}</div>\"\n",
    "        html_output += f\"</div>\"\n",
    "\n",
    "        agent_name: str | None = None\n",
    "        full_response = \"\"\n",
    "        function_calls = []\n",
    "        function_results = {}\n",
    "\n",
    "        # Collect the agent's response with function call tracking\n",
    "        async for content in agent.invoke_stream(chat_history):\n",
    "            if not agent_name and hasattr(content, 'name'):\n",
    "                agent_name = content.name\n",
    "\n",
    "            # Track function calls and results\n",
    "            for item in content.items:\n",
    "                if isinstance(item, FunctionCallContent):\n",
    "                    call_info = f\"Calling: {item.function_name}({item.arguments})\"\n",
    "                    function_calls.append(call_info)\n",
    "                elif isinstance(item, FunctionResultContent):\n",
    "                    result_info = f\"Result: {item.result}\"\n",
    "                    function_calls.append(result_info)\n",
    "                    # Store function results\n",
    "                    function_results[item.function_name] = item.result\n",
    "\n",
    "            # Add content to response if it's not a function-related message\n",
    "            if (hasattr(content, 'content') and content.content and content.content.strip() and\n",
    "                not any(isinstance(item, (FunctionCallContent, FunctionResultContent))\n",
    "                        for item in content.items)):\n",
    "                full_response += content.content\n",
    "\n",
    "        # Add function calls to HTML if any occurred\n",
    "        if function_calls:\n",
    "            html_output += f\"<div style='margin-bottom:10px'>\"\n",
    "            html_output += f\"<details>\"\n",
    "            html_output += f\"<summary style='cursor:pointer; font-weight:bold; color:#0066cc;'>Function Calls (click to expand)</summary>\"\n",
    "            html_output += f\"<div style='margin:10px; padding:10px; background-color:#f8f8f8; border:1px solid #ddd; border-radius:4px; white-space:pre-wrap;'>\"\n",
    "            html_output += \"<br>\".join(function_calls)\n",
    "            html_output += f\"</div></details></div>\"\n",
    "\n",
    "        # Add agent response to HTML\n",
    "        html_output += f\"<div style='margin-bottom:20px'>\"\n",
    "        html_output += f\"<div style='font-weight:bold'>{agent_name or 'Assistant'}:</div>\"\n",
    "        html_output += f\"<div style='margin-left:20px; white-space:pre-wrap'>{full_response}</div>\"\n",
    "        html_output += f\"</div>\"\n",
    "        html_output += \"<hr>\"\n",
    "\n",
    "        # Display formatted HTML\n",
    "        display(HTML(html_output))\n",
    "\n",
    "await main()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
